import { App, ObjectPlugin, getCurrentInstance } from 'vue'

interface StackRecord {
    name:string
    link:string
    row:number
    column:number
}

const strToStackRecord=(stackStr:string):StackRecord=>{
    let result:StackRecord={name:"",link:"",row:-1,column:-1}
    let reg:RegExp=new RegExp("(?<=\\()(.+?)(?=\\))")
    let searched=reg.exec(stackStr)
    if (searched&&searched.length>0){
        result.link=searched[0]
        let startIndex=stackStr.indexOf("at ")
        let endIndex=stackStr.indexOf("(")
        result.name=stackStr.substring(startIndex+3,endIndex).trim()
    }
    else
    {
        result.link=stackStr.replace("at ","").trim()
    }
    let lines=result.link.split(':')
    if (lines&&lines.length>2){
        result.column=Number.parseInt(lines[lines.length-1])
        result.row=Number.parseInt(lines[lines.length-2])
    }
    return result
}

const stackTrace=():Array<StackRecord>=>{
    let result=new Array<StackRecord>()
    let stack=""
    try{
        throw new Error("____magic9527")
    }
    catch(e:any){
        stack+=""+e.stack
    }
    if (stack){
        let stackTrace=stack.split("\n")
        for (let i in stackTrace){
            result.push(strToStackRecord(stackTrace[i]))
        }
    }
    return result
}

const findCallStackRecord=():StackRecord=>{
    let traces=stackTrace()
    return traces[4]
}

type StateChangeCallback=(key:string,oldValue:any,newValue:any)=>void
interface StateWatcher{
    keys:Array<string>
    callback:StateChangeCallback
}

class States implements ObjectPlugin {

    readonly states:Map<string,any>
    readonly watcherMap:Map<Number,StateWatcher>

    constructor(){
        this.states=new Map<string,any>()
        this.watcherMap=new Map<Number,StateWatcher>
    }

    getState=(key:string,defValue?:any):any=>{
        if (this.has(key)){
            return this.states.get(key)
        }
        let value=localStorage.getItem(key)
        if (value){
            let objValue=JSON.parse(value)
            this.states.set(key,objValue)
            return objValue
        }
        return defValue
    }

    private doChange(key:string,oldValue:any,newValue:any){
        this.watcherMap.forEach((value)=>{
            try{
                let watcher=value
                if (watcher&&watcher.keys.includes(key)){
                    watcher.callback(key,oldValue,newValue)
                }
            }
            catch(e){
                console.error(e)
            }
        })
    }

    setState=(key:string,value:any,persistence?:boolean):void=>{
        let oldValue=this.getState(key,null)
        if (oldValue!=value){
            console.log("State machine set state key="+key+" value="+value+" call from:",findCallStackRecord().link)
            this.states.set(key,value)
            this.doChange(key,oldValue,value)
            if (persistence){
                new Promise(()=>{
                    localStorage.setItem(key,JSON.stringify(value))
                })
            }
        }
    }

    delete=(key:string):void=>{
        if (this.states.has(key)){
            this.states.delete(key)
            let oldValue=this.getState(key,null)
            this.doChange(key,oldValue,undefined)
        }
    }

    has=(key:string):boolean=>{
        return this.states.has(key)
    }

    watch=(watcher:StateWatcher):void=>{
        let instance=getCurrentInstance()
        if (instance&&!this.watcherMap.has(instance.uid)){
            this.watcherMap.set(instance.uid,watcher)
        }
    }

    install(app:App,options:any[]){
        let map=this.watcherMap
        app.mixin({
            unmounted(){
                let instance=getCurrentInstance()
                if (instance&&instance.uid){
                    map.delete(instance.uid)
                }
            }
        })
    }
}

const states=new States()
const StatesUtils=states;
export { StatesUtils, StateWatcher, StateChangeCallback }
export default states